Skip to content

[chores] Added support for user expiration reminders #608#615

Merged
nemesifier merged 4 commits into
masterfrom
issues/608-celery-beat-schedule
May 28, 2026
Merged

[chores] Added support for user expiration reminders #608#615
nemesifier merged 4 commits into
masterfrom
issues/608-celery-beat-schedule

Conversation

@pandafy

@pandafy pandafy commented May 14, 2026

Copy link
Copy Markdown
Member

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • N/A I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #608

Description of Changes

  • Added celery beat schedule for expiration reminder emails
  • Added support for openwisp_users expiration tasks
  • Added configurable reminder email schedule variable

Blockers

@pandafy pandafy self-assigned this May 14, 2026
Comment thread defaults/main.yml Outdated
Comment on lines +19 to +21
# TODO: Remove before merging
- "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
- "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Remove before merging!

Comment thread defaults/main.yml Outdated
- bpython
# TODO: Remove before merging
- "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
- "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: TODO items must be resolved before merging. Temporary tarball dependencies should not be merged to master.

Comment thread templates/openwisp2/settings.py Outdated
"max_retries": {{ openwisp2_celery_broker_max_tries }},
}

CELERY_TIMEZONE = TIME_ZONE

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The CELERY_TIMEZONE = TIME_ZONE setting is added here, but TIME_ZONE is defined later in the file (around line 420). While Python handles this at runtime, for clarity consider placing this after TIME_ZONE is defined, or add a comment noting it's a Django setting that will be resolved at runtime.

"schedule": crontab(**{ {{ cron_password_expiration_email }} }),
},
{% endif %}
"deactivate_expired_users": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: The deactivate_expired_users task has been moved from openwisp_radius (conditional on openwisp2_radius and openwisp2_radius_periodic_tasks) to openwisp_users (always enabled). This changes behavior for non-RADIUS installations. Ensure this is intentional and that openwisp_users.tasks.deactivate_expired_users exists in all supported openwisp-users versions.

"schedule": crontab(**{ {{ cron_deactivate_expired_users }} }),
"args": None,
},
"expiration_reminder_email": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: The expiration_reminder_email task runs unconditionally. Ensure openwisp_users.tasks.expiration_reminder_email exists in all supported openwisp-users versions, or wrap this in a conditional check similar to password_expiration_email above.

@kilo-code-bot

kilo-code-bot Bot commented May 14, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Observations | Recommendation: Review task placement before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
templates/openwisp2/settings.py 255 deactivate_expired_users task runs unconditionally - verify if intended for all installations
templates/openwisp2/settings.py 260 expiration_reminder_email task runs unconditionally - ensure task exists in all supported versions
Resolved Issues (click to expand)

FIXED

File Line Issue
defaults/main.yml 19-21 TODO items and temporary tarball dependencies - FIXED: Removed in commit 0163d6d
templates/openwisp2/settings.py 420 CELERY_TIMEZONE placement - FIXED: Now correctly placed after TIME_ZONE definition
Other Observations (not in diff)
  1. Behavior Change: The deactivate_expired_users task was previously conditional on openwisp2_radius and openwisp2_radius_periodic_tasks, but is now always enabled. This may affect non-RADIUS installations.

  2. Task Order: The reminder email (minute 3) now runs AFTER deactivation (minute 1), which reverses the previous logic where deactivation was at minute 5. This appears intentional - deactivation happens after reminders have been sent.

  3. Documentation Updated: The role-variables.rst documentation correctly reflects the new cron variables.

Files Reviewed (3 files)
  • defaults/main.yml - FIXED: TODO/temporary dependencies removed
  • templates/openwisp2/settings.py - 2 suggestions (task placement verification recommended)
  • templates/load_initial_data.py - No changes in this review

Issue Alignment Check: This PR implements the changes described in #608 - adding celery beat schedules for expiration reminders and user deactivation tasks.

Testing Recommendation: Since this PR modifies celery beat schedules and moves tasks between modules, ensure integration tests verify:

  1. The tasks execute correctly in both RADIUS and non-RADIUS installations
  2. No duplicate task registrations occur
  3. The reminder email task exists in the openwisp-users package

Reviewed by gpt-5.4-20260305 · 19,685 tokens

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c2502a3-48a3-4ff5-8018-4ce6a3f5e253

📥 Commits

Reviewing files that changed from the base of the PR and between 8204f0c and 8a62054.

📒 Files selected for processing (2)
  • defaults/main.yml
  • templates/openwisp2/settings.py
💤 Files with no reviewable changes (1)
  • templates/openwisp2/settings.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build ubuntu2404
  • GitHub Check: Build debian13
  • GitHub Check: Build debian12
  • GitHub Check: Build ubuntu2204
🔇 Additional comments (1)
defaults/main.yml (1)

216-216: LGTM!


📝 Walkthrough

Walkthrough

This PR updates periodic scheduling for user account expiration: adds a new cron variable cron_expiration_reminder_email at 00:03, changes cron_deactivate_expired_users to 00:01, consolidates both tasks into the main CELERY_BEAT_SCHEDULE (removing the radius-scoped entry), and sets CELERY_TIMEZONE = TIME_ZONE.

Sequence Diagram(s)

sequenceDiagram
  participant SettingsTemplate
  participant CeleryBeat
  participant openwisp_users.deactivate as openwisp_users.tasks.deactivate_expired_users
  participant openwisp_users.reminder as openwisp_users.tasks.expiration_reminder_email
  participant openwisp2_radius

  SettingsTemplate->>CeleryBeat: register deactivate_expired_users (crontab)
  SettingsTemplate->>CeleryBeat: register expiration_reminder_email (crontab)
  CeleryBeat->>openwisp_users.deactivate: run at 00:01
  CeleryBeat->>openwisp_users.reminder: run at 00:03
  SettingsTemplate->>openwisp2_radius: remove radius-scoped deactivate task
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format [type] with 'chores' and provides a descriptive summary of the main changes (user expiration reminders), with issue reference.
Description check ✅ Passed The description includes all major required sections: completed checklist, issue reference (#608), clear description of changes, and identifies blockers. Test case section marked N/A is appropriately noted.
Linked Issues check ✅ Passed The PR addresses all acceptance criteria from #608: uses openwisp-users task for deactivation, keeps cron_deactivate_expired_users variable unchanged, adds expiration reminder email support, and updates documentation.
Out of Scope Changes check ✅ Passed All changes align with issue #608 objectives: cron schedule updates, Celery beat configuration for user expiration tasks, new reminder email variable, CELERY_TIMEZONE alignment, and documentation updates are all in scope.
Bug Fixes ✅ Passed This PR is not a bug fix but a feature/chore PR that adds user expiration reminder support and updates Celery beat schedule configuration. The check for bug fixes is not applicable here.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issues/608-celery-beat-schedule

Comment @coderabbitai help to get the list of available commands and usage tips.

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
templates/openwisp2/settings.py (1)

248-248: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix import-time name resolution bug for CELERY_TIMEZONE.

Line 248 references TIME_ZONE before it is assigned (Line 420), which can fail at settings import time.

Proposed fix
-CELERY_TIMEZONE = TIME_ZONE
+CELERY_TIMEZONE = "{{ openwisp2_time_zone }}"
#!/bin/bash
python - <<'PY'
from pathlib import Path

p = Path("templates/openwisp2/settings.py")
lines = p.read_text().splitlines()

celery_tz_line = next(i+1 for i, l in enumerate(lines) if l.strip().startswith("CELERY_TIMEZONE"))
time_zone_line = next(i+1 for i, l in enumerate(lines) if l.strip().startswith("TIME_ZONE ="))

print(f"CELERY_TIMEZONE line: {celery_tz_line}")
print(f"TIME_ZONE line: {time_zone_line}")
print("time_zone_defined_before_reference:", time_zone_line < celery_tz_line)
PY
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/openwisp2/settings.py` at line 248, CELERY_TIMEZONE is referencing
TIME_ZONE before it's assigned; update settings so TIME_ZONE is defined prior to
using it (move the CELERY_TIMEZONE assignment to after the TIME_ZONE = ... line)
or change CELERY_TIMEZONE to read TIME_ZONE safely (e.g. via
globals().get('TIME_ZONE', '<default>')) so CELERY_TIMEZONE and TIME_ZONE in
templates/openwisp2/settings.py are resolved at import time; edit the
CELERY_TIMEZONE assignment or its position and ensure the symbols
CELERY_TIMEZONE and TIME_ZONE are consistently ordered or accessed safely.
defaults/main.yml (1)

19-21: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove temporary branch tarball dependencies from defaults before merge.

Line 19 explicitly marks this as temporary, and Line 20-Line 21 pin default installs to mutable GitHub issue branches, which is not safe/reproducible for master.

Proposed fix
 openwisp2_extra_python_packages:
   - bpython
-  # TODO: Remove before merging
-  - "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
-  - "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@defaults/main.yml` around lines 19 - 21, Remove the temporary GitHub tarball
pins from defaults/main.yml: delete the entries containing
"openwisp-radius[openvpn_status] @
https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
and "openwisp-users @
https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"
and replace them with stable, reproducible pins (either the package name alone
or a released version spec, e.g., openwisp-radius==<released_version> and
openwisp-users==<released_version>) so defaults no longer reference mutable
issue-branch tarballs before merging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@defaults/main.yml`:
- Around line 19-21: Remove the temporary GitHub tarball pins from
defaults/main.yml: delete the entries containing
"openwisp-radius[openvpn_status] @
https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
and "openwisp-users @
https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"
and replace them with stable, reproducible pins (either the package name alone
or a released version spec, e.g., openwisp-radius==<released_version> and
openwisp-users==<released_version>) so defaults no longer reference mutable
issue-branch tarballs before merging.

In `@templates/openwisp2/settings.py`:
- Line 248: CELERY_TIMEZONE is referencing TIME_ZONE before it's assigned;
update settings so TIME_ZONE is defined prior to using it (move the
CELERY_TIMEZONE assignment to after the TIME_ZONE = ... line) or change
CELERY_TIMEZONE to read TIME_ZONE safely (e.g. via globals().get('TIME_ZONE',
'<default>')) so CELERY_TIMEZONE and TIME_ZONE in
templates/openwisp2/settings.py are resolved at import time; edit the
CELERY_TIMEZONE assignment or its position and ensure the symbols
CELERY_TIMEZONE and TIME_ZONE are consistently ordered or accessed safely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0fb0316e-f344-4d78-bbfd-81729ba4734b

📥 Commits

Reviewing files that changed from the base of the PR and between 817a4a0 and f3e0020.

📒 Files selected for processing (3)
  • defaults/main.yml
  • docs/user/role-variables.rst
  • templates/openwisp2/settings.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build debian12
  • GitHub Check: Build debian13
  • GitHub Check: Build ubuntu2404
  • GitHub Check: Build ubuntu2204
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (Custom checks)

**/*.{md,rst,txt}: Changes: If the change affects behavior that is already documented, the documentation must be updated to reflect the new behavior
Features: Documentation must mention the new feature; for heavily UI-related features, a new section or page is appropriate

Files:

  • docs/user/role-variables.rst
**/*.py

📄 CodeRabbit inference engine (Custom checks)

General: For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • templates/openwisp2/settings.py
🔇 Additional comments (3)
defaults/main.yml (1)

217-218: LGTM!

docs/user/role-variables.rst (1)

450-451: LGTM!

templates/openwisp2/settings.py (1)

256-264: LGTM!

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 14, 2026
@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3).

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (3/3).

@openwisp-companion

Copy link
Copy Markdown

Molecule Destroy Failure

Hello @pandafy,
(Analysis for commit f3e0020)

The Molecule destroy scenario failed because the instances could not be deleted. This is likely a transient infrastructure issue. The CI has been restarted automatically. If the issue persists, please report it.

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please send a separate PR to instruct the role to install the latest version of the modules? This will allow us to merge not only this PR but also others that are waiting for this.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@defaults/main.yml`:
- Around line 11-16: The current defaults define package sources using floating
tarball URLs (openwisp2_controller_version, openwisp2_network_topology_version,
openwisp2_firmware_upgrader_version, openwisp2_monitoring_version,
openwisp2_radius_version) which, combined with ansible.builtin.pip usage with
state: latest in tasks/pip.yml, makes installs non-reproducible; update each of
those variables to point to immutable refs (released version strings or tarball
URLs containing commit SHAs or release tags) instead of .../tarball/master so
repeated runs install the exact same artifact.

In `@templates/load_initial_data.py`:
- Around line 57-64: The current broad JSON text search and unconditional access
to template_obj.config["files"] is unsafe; tighten the predicate to search for
the JSON key/value pair (e.g. config_text__icontains='"path":
"/etc/dropbear/authorized_keys"') so you only match objects that declare that
path, and when iterating use template_obj.config.get("files", []) and check each
file dict for the presence and types of "path" and "contents" (e.g. ensure
file.get("path") == "/etc/dropbear/authorized_keys" and "contents" in file and
isinstance(file["contents"], str)) before reading or using those values to avoid
KeyError/TypeError in the code that processes files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0a12cbc8-c741-441a-b1bd-aba263ce1365

📥 Commits

Reviewing files that changed from the base of the PR and between f3e0020 and 1d8b00c.

📒 Files selected for processing (4)
  • defaults/main.yml
  • docs/user/role-variables.rst
  • templates/load_initial_data.py
  • templates/openwisp2/settings.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build debian13
  • GitHub Check: Build ubuntu2404
  • GitHub Check: Build ubuntu2204
  • GitHub Check: Build debian12
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (Custom checks)

**/*.{md,rst,txt}: Changes: If the change affects behavior that is already documented, the documentation must be updated to reflect the new behavior
Features: Documentation must mention the new feature; for heavily UI-related features, a new section or page is appropriate

Files:

  • docs/user/role-variables.rst
**/*.py

📄 CodeRabbit inference engine (Custom checks)

General: For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • templates/load_initial_data.py
  • templates/openwisp2/settings.py
🔇 Additional comments (4)
templates/load_initial_data.py (1)

19-20: LGTM!

defaults/main.yml (1)

218-219: LGTM!

docs/user/role-variables.rst (1)

450-451: LGTM!

templates/openwisp2/settings.py (1)

255-263: LGTM!

Also applies to: 420-420

Comment thread defaults/main.yml
Comment on lines +11 to +16
# TODO: Update when releasing OpenWISP 26
openwisp2_controller_version: "openwisp-controller @ https://github.com/openwisp/openwisp-controller/tarball/master"
openwisp2_network_topology_version: "openwisp-network-topology @ https://github.com/openwisp/openwisp-network-topology/tarball/master"
openwisp2_firmware_upgrader_version: "openwisp-firmware-upgrader @ https://github.com/openwisp/openwisp-firmware-upgrader/tarball/master"
openwisp2_monitoring_version: "openwisp-monitoring @ https://github.com/openwisp/openwisp-monitoring/tarball/master"
openwisp2_radius_version: "openwisp-radius @ https://github.com/openwisp/openwisp-radius/tarball/master"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify floating refs in defaults and how they are installed.
rg -n 'tarball/(master|issues/)' defaults/main.yml
rg -n -C2 'Install extra python packages|openwisp2_extra_python_packages|state:\s*latest' tasks/pip.yml

Repository: openwisp/ansible-openwisp2

Length of output: 2503


Pin OpenWISP package sources to immutable refs.

defaults/main.yml lines 12-16 use floating direct URLs (.../tarball/master) for openwisp-controller, openwisp-network-topology, openwisp-firmware-upgrader, openwisp-monitoring, and openwisp-radius. These are then installed via ansible.builtin.pip with state: latest in tasks/pip.yml, so rerunning the role can pull different upstream contents from master, breaking reproducibility (and potentially behavior). Pin to released versions or immutable commit/archive URLs (e.g., commit SHA tarballs).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@defaults/main.yml` around lines 11 - 16, The current defaults define package
sources using floating tarball URLs (openwisp2_controller_version,
openwisp2_network_topology_version, openwisp2_firmware_upgrader_version,
openwisp2_monitoring_version, openwisp2_radius_version) which, combined with
ansible.builtin.pip usage with state: latest in tasks/pip.yml, makes installs
non-reproducible; update each of those variables to point to immutable refs
(released version strings or tarball URLs containing commit SHAs or release
tags) instead of .../tarball/master so repeated runs install the exact same
artifact.

Comment on lines +57 to 64
queryset = (
Template.objects.annotate(
# Cast config to text because JSONField __contains lookup is not
# supported on all database backends (e.g. SQLite).
config_text=Cast("config", output_field=TextField())
)
.filter(config_text__icontains="/etc/dropbear/authorized_keys")
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant section with line numbers
FILE="templates/load_initial_data.py"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "== $FILE (lines 1-140) =="
nl -ba "$FILE" | sed -n '1,140p'

echo
echo "== $FILE (lines 140-260) =="
nl -ba "$FILE" | sed -n '140,260p'

echo
echo "== Search for config[\"files\"] usage =="
rg -n "config\[\s*\"files\"\s*\]" -S "$FILE" || true

echo
echo "== Search for template_obj.config usage =="
rg -n "template_obj\.config" -S "$FILE" || true

echo
echo "== Search for Template.objects.annotate(.filter(config_text) in repo =="
rg -n "config_text__icontains" -S . || true

Repository: openwisp/ansible-openwisp2

Length of output: 183


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="templates/load_initial_data.py"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "== $FILE (1-220) =="
cat -n "$FILE" | sed -n '1,220p'

echo
echo "== Search for config[\"files\"] usage =="
rg -n "config\[\s*\"files\"\s*\]" -S "$FILE" || true

echo
echo "== Search for template_obj.config usage =="
rg -n "template_obj\.config" -S "$FILE" || true

echo
echo "== Search for config_text__icontains usage =="
rg -n "config_text__icontains" -S "$FILE" || true

Repository: openwisp/ansible-openwisp2

Length of output: 4125


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for the Template model behind swapper load_model("config", "Template")
# by looking for "class Template" and "load_model("config", "Template")" and "config = models.JSONField"
rg -n "load_model\(\"config\", \"Template\"\)" -S . || true
rg -n "class Template\b" -S . || true
rg -n "Template.*models\.JSONField|models\.JSONField.*Template|config\s*=\s*models\.JSONField" -S . || true

echo
echo "== Find likely Template model files =="
# Use fd to list model files potentially containing Template
fd -e py -t f -g '*models*' . | head -n 50 || true

# Search within any likely config/template model files
fd -e py -t f -g '*config*' . | head -n 200 | xargs -I{} sh -c 'rg -n "class Template\b" "{}" || true' || true

Repository: openwisp/ansible-openwisp2

Length of output: 190


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for authorized_keys path occurrences =="
rg -n "/etc/dropbear/authorized_keys" -S . || true

echo
echo "== Search for config schema examples containing \"files\" =="
rg -n "\"files\"" -S . || true

echo
echo "== Search for template config mutation/access patterns =="
rg -n "config\[\s*\"files\"\s*\]|config\.get\(\s*\"files\"" -S . || true

Repository: openwisp/ansible-openwisp2

Length of output: 792


Avoid broad text matching for JSON config; guard template_obj.config["files"] access

config_text__icontains="/etc/dropbear/authorized_keys" can match unrelated JSON content, but later code unconditionally does template_obj.config["files"] (KeyError risk). Add defensive access (iterate over template_obj.config.get("files", []), and guard path/contents) and tighten the text predicate to look for the "path" key/value pair rather than only the raw authorized_keys string.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/load_initial_data.py` around lines 57 - 64, The current broad JSON
text search and unconditional access to template_obj.config["files"] is unsafe;
tighten the predicate to search for the JSON key/value pair (e.g.
config_text__icontains='"path": "/etc/dropbear/authorized_keys"') so you only
match objects that declare that path, and when iterating use
template_obj.config.get("files", []) and check each file dict for the presence
and types of "path" and "contents" (e.g. ensure file.get("path") ==
"/etc/dropbear/authorized_keys" and "contents" in file and
isinstance(file["contents"], str)) before reading or using those values to avoid
KeyError/TypeError in the code that processes files.

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3).

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3).

Comment thread defaults/main.yml Outdated
Comment on lines +21 to +22
- "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
- "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
- "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"

pandafy added 3 commits May 27, 2026 13:28
- Added celery beat schedule for expiration reminder emails
- Added support for openwisp_users expiration tasks
- Added configurable reminder email schedule variable

Closes #608
@pandafy pandafy force-pushed the issues/608-celery-beat-schedule branch from 0163d6d to 8204f0c Compare May 27, 2026 08:15
@pandafy

pandafy commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review!

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read all changed files
echo "=== defaults/main.yml ==="
cat defaults/main.yml

echo ""
echo "=== templates/openwisp2/settings.py ==="
cat templates/openwisp2/settings.py

echo ""
echo "=== docs/user/role-variables.rst ==="
cat docs/user/role-variables.rst

echo ""
echo "=== PR diff ==="
gh pr diff 615 --repo openwisp/ansible-openwisp2

Length of output: 60219



@pandafy: Here is a full review of the changes in this PR.


Overall Assessment

The PR correctly moves deactivate_expired_users from openwisp_radius.tasks to openwisp_users.tasks (making it unconditional and available even without RADIUS), adds the new expiration_reminder_email periodic task, and importantly adds CELERY_TIMEZONE = TIME_ZONE to keep Celery crontab schedules aligned with Django's timezone. The changes are clean and the documentation is kept in sync.

A few issues worth addressing:


Issue 1 — "args": None should be omitted

File: templates/openwisp2/settings.py

"deactivate_expired_users": {
    "task": "openwisp_users.tasks.deactivate_expired_users",
    "schedule": crontab(**{ {{ cron_deactivate_expired_users }} }),
    "args": None,   # ← non-standard
},

In Celery beat, the args key is expected to be a tuple or list. Passing None can cause a TypeError depending on the Celery version (TypeError: 'NoneType' object is not iterable). The original radius-scoped entry also had "args": None, so this bug was carried over. The fix is simply to omit the key:

"deactivate_expired_users": {
    "task": "openwisp_users.tasks.deactivate_expired_users",
    "schedule": crontab(**{ {{ cron_deactivate_expired_users }} }),
},

Issue 2 — Missing openwisp2_users_periodic_tasks toggle (consistency)

Both openwisp2_monitoring_periodic_tasks and openwisp2_radius_periodic_tasks exist to allow operators to disable periodic tasks for those modules. The two new openwisp_users tasks are always registered unconditionally, breaking this pattern:

{% if openwisp2_radius and openwisp2_radius_periodic_tasks %}
    "delete_old_radiusbatch_users": { ... },
{% endif %}

Consider adding a openwisp2_users_periodic_tasks: true variable in defaults/main.yml and guarding the two new tasks behind it:

{% if openwisp2_users_periodic_tasks %}
    "deactivate_expired_users": { ... },
    "expiration_reminder_email": { ... },
{% endif %}

This gives operators the same flexibility they have for the other modules.


Issue 3 — Variable ordering in defaults/main.yml is slightly misleading

The variables are listed in this order:

cron_delete_old_notifications: "'hour': 0, 'minute': 0"
cron_expiration_reminder_email: "'hour': 0, 'minute': 3"   # runs at :03
cron_deactivate_expired_users: "'hour': 0, 'minute': 1"    # runs at :01

cron_deactivate_expired_users (minute 1) runs before cron_expiration_reminder_email (minute 3), but it appears second in the file. This is a minor readability nit — listing them in chronological order (minute 1 then minute 3) would make the scheduling intent clearer.


Minor — CELERY_TIMEZONE placement

CELERY_TIMEZONE = TIME_ZONE is placed after TIME_ZONE which is correct. One small thing: the blank line added after it creates a double blank line before {% if openwisp2_internationalization %}. Not a functional issue, just cosmetic.


Note on upstream blockers

The PR mentions it depends on openwisp-users/pull/517 and openwisp-radius/pull/719. Make sure the openwisp_users.tasks.expiration_reminder_email and updated openwisp_users.tasks.deactivate_expired_users task signatures are finalised in those PRs before merging this one, to avoid a broken deployment for anyone pinned to a recent release.

@nemesifier nemesifier self-assigned this May 28, 2026
@github-project-automation github-project-automation Bot moved this from Backlog to In progress in 26.06 Release May 28, 2026
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Actionable comments posted: 0

@nemesifier nemesifier merged commit 500126e into master May 28, 2026
8 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in 26.06 Release May 28, 2026
@nemesifier nemesifier deleted the issues/608-celery-beat-schedule branch May 28, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[chore] Update celery beat schedule for user expiration logic

2 participants